﻿#pragma once

#include <atomic>
#include <ctime>
#include <list>
#include <memory>
#include <mutex>
#include <thread>
#include <unordered_map>

namespace kratos {

/**
 * 固定大小的内存池.
 */
class FixedMemPool {
  std::mutex         lock_;              ///< 线程锁
  std::list<char *>  ptr_list_;          ///< 可用内存块链表
  std::size_t        fixed_size_{0};     ///< 池内内存块大小，字节
  std::atomic_size_t count_in_use_{0};   ///< 已分配并正在使用的内存块数量
  std::time_t        last_gc_tick_{0};   ///< 上一次GC的时间戳，毫秒
  std::time_t        gc_intval_{0};      ///< GC周期
  std::size_t        min_count_{0};      ///< 池内内存持有最小数量
  std::size_t        recycle_step_{0};   ///< 每轮GC的数量
  bool               gc_on_off_{ true }; ///< GC开关

public:
  /**
   * 构造.
   *
   * \param fixed_size 固定长度，字节
   */
  FixedMemPool(std::size_t fixed_size) noexcept;
  /**
   * 析构.
   *
   */
  ~FixedMemPool() noexcept;
  /**
   * 逻辑主循环.
   *
   * \param now 当前时间戳，毫秒
   * \return
   */
  auto update(std::time_t now) noexcept -> void;
  /**
   * 分配一个内存块.
   *
   * \return 内存块地址
   */
  auto rent() noexcept -> char *;
  /**
   * 回收一个内存块.
   *
   * \param ptr 内存块地址
   * \return true或false
   */
  auto recycle(char *ptr) noexcept(false) -> bool;
  /**
   * 获取被外部持的内存块(正在使用)总量.
   *
   * \return
   */
  auto count_in_use() const noexcept -> std::size_t;
  /**
   * 获取池内内存块长度，字节.
   *
   * \return
   */
  auto fixed_size() const noexcept -> std::size_t;
  /**
   * 池内剩余未被使用的内存块数量.
   *
   * \return
   */
  auto count_in_pool() noexcept -> std::size_t;
  /**
   * 释放所有池内未被使用的内存块.
   *
   * \return
   */
  auto release_all() noexcept -> void;
  /**
   * 设置GC间隔，毫秒 
   */
  auto set_gc_intval(std::time_t intval) noexcept -> void;
  /**
   * 设置GC开关
   *
   * \param on_off 开关
   */
  auto turn_on_off_gc(bool on_off) noexcept -> void;

private:
  /**
   * 运行一次GC.
   *
   * \return
   */
  auto step_gc() noexcept -> void;
  /**
   * 从堆上分配并初始化一个内存块.
   *
   * \return
   */
  auto init_one() noexcept -> char *;
};

/**
 * 内存池.
 */
class MemPool {
  using MemMap = std::unordered_map<std::size_t, std::unique_ptr<FixedMemPool>>;

  MemMap             mem_map_;                  ///< 多个不同大小的内存池
  std::size_t        max_size_{0};              ///< 内存池所支持的最大内存分配长度，超过则直接从堆上分配，回池后直接释放
  std::atomic_size_t count_in_use_{0};          ///< 外部持有的内存块数量, 字节
  std::atomic_size_t mem_block_size_in_use_{0}; ///< 外部持有的内存总量, 字节

  constexpr static std::size_t MIN_SIZE = 16;        ///< 默认值，池内可以分配的最小长度, 如果小于这个长度也将按这个长度分配
  constexpr static std::size_t MAX_SIZE = 1024 * 16; ///< 默认值，池内可以分配的最大长度

  bool        gc_on_off_{true}; ///< GC开关
  std::time_t gc_intval_{0};    ///< GC周期

public:
  /**
   * 构造.
   * 
   * \param max_size 池内可以分配的最大长度，应为2幂, 如果不是则自动调整为2的幂
   */
  MemPool(std::size_t max_size = MAX_SIZE) noexcept;
  /**
   * 析构.
   * 
   */
  ~MemPool() noexcept;
  /**
   * 主循环.
   * 
   * \param now 当前时间戳
   * \return 
   */
  auto update(std::time_t now) noexcept -> void;
  /**
   * 内存分配.
   * 
   * \param size 长度
   * \return 内存块地址
   */
  auto rent(std::size_t size) noexcept(false) -> char *;
  /**
   * 回收.
   * 
   * \param ptr 内存块地址
   * \return true或false
   */
  auto recycle(char *ptr) noexcept(false) -> bool;
  /**
   * 获取正在被外部使用的内存块数量.
   * 
   * \return 
   */
  auto count_in_use() const noexcept -> std::size_t;
  /**
   * 正在被外部使用的内存总量，字节.
   * 
   * \return 
   */
  auto mem_block_size_in_use() const noexcept -> std::size_t;
  /**
   * 获取池内所有固定长度的内存池.
   * 
   * \return 
   */
  auto get_fixed_mem_pools() noexcept -> const MemMap &;
  /**
   * 释放所有未被使用池内内存块.
   * 
   * \return 
   */
  auto release_all() noexcept -> void;
  /**
   * 获取GC开关
   *
   * \return true或者false
   */
  auto get_gc_on_off() const noexcept -> bool;
  /**
   * 设置GC开关
   *
   * \param on_off 开关
   */
  auto turn_on_off_gc(bool on_off) noexcept -> void;
  /**
   * 设置GC间隔，毫秒
   *
   * \param intval 间隔，毫秒
   */
  auto set_gc_intval(std::time_t intval) noexcept -> void;
  /**
   * 获取GC间隔，毫秒 
   * 
   * \return GC间隔，毫秒
   */
  auto get_gc_intval() const noexcept -> std::time_t;
};

/**
 * 获取容器全局内存池.
 */
extern kratos::MemPool *get_global_mem_pool();

#define MempoolRef (*get_global_mem_pool())

} // namespace kratos
